package no.ntnu.fp.swingutil;

import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * @author Andreas H. Pedersen
 *
 */
public class CalendarTools {
	
	private Calendar calendar;
	
	/**
	 * Constructs a <code>Calendar</code> object with the first day of the week set to Monday
	 */
	public CalendarTools() {
		calendar = Calendar.getInstance();
		calendar.setFirstDayOfWeek(Calendar.MONDAY);
	}
	
	/**
	 * Converts the time represented by the given <code>Date</code> object to milliseconds
	 * @param date - the time represented as a <code>Date</code> object
	 * @return <code>long</code> representing the time in milliseconds
	 */
	public long getDateAsLong(Date date) {
		calendar.setTime(date);
		return calendar.getTimeInMillis();
	}
	
	/**
	 * Converts the time represented by the given <code>long</code> to a <code>Date</code> representing the same time
	 * @param date - <code>long</code> representing the time
	 * @return <code>Date</code> representing the time
	 */
	public Date getLongAsDate(long date) {
		calendar.setTimeInMillis(date);
		return calendar.getTime();
	}
	
	/**
	 * Returns the time right now. If any of the <code>boolean</code>s given as input is set to false,
	 * the corresponding date field will be set to 0. For day of month it will be set to 1.
	 * @param year - <code>boolean</code>
	 * @param month - <code>boolean</code>
	 * @param dayOfMonth - <code>boolean</code>
	 * @param hourOfDay - <code>boolean</code>
	 * @param minute - <code>boolean</code>
	 * @param second - <code>boolean</code>
	 * @param millisecond - <code>boolean</code>
	 * @return <code>Date</code> representing the current time
	 */
	public Date getNow(boolean year, boolean month, boolean dayOfMonth, boolean hourOfDay, boolean minute, boolean second, boolean millisecond) {
		calendar.setTime(new Date());
		if ( !year ) calendar.set(Calendar.YEAR,0);
		if ( !month ) calendar.set(Calendar.MONTH,0);
		if ( !dayOfMonth ) calendar.set(Calendar.DAY_OF_MONTH,1);
		if ( !hourOfDay ) calendar.set(Calendar.HOUR_OF_DAY,0);
		if ( !minute ) calendar.set(Calendar.MINUTE,0);
		if ( !second ) calendar.set(Calendar.SECOND,0);
		if ( !millisecond ) calendar.set(Calendar.MILLISECOND,0);
		return calendar.getTime();
	}
	
	/**
	 * Returns a <code>Date</code> with the time set to the given year, month and day of month.
	 * Hour, minute, second and millisecond is set to 0.<br>
	 * <b>Note</b>: The first month <code>JANUARY</code> has the value 0.
	 * @param year - <code>int</code>
	 * @param month - <code>int</code>
	 * @param dayOfMonth - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date getDate(int year, int month, int dayOfMonth) {
		calendar.clear();
		calendar.set(year, month, dayOfMonth);
		return calendar.getTime();
	}
	
	/**
	 * Returns a <code>Date</code> with the time set to the given year, month, day of month, hour and minute.
	 * Second and millisecond is set to 0.<br>
	 * <b>Note</b>: The first month <code>JANUARY</code> has the value 0.
	 * @param year - <code>int</code>
	 * @param month - <code>int</code>
	 * @param dayOfMonth - <code>int</code>
	 * @param hour - <code>int</code>
	 * @param minute - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date getDate(int year, int month, int dayOfMonth, int hour, int minute) {
		calendar.clear();
		calendar.set(year, month, dayOfMonth, hour, minute);
		return calendar.getTime();
	}

	/**
	 * Returns the minute of the given <code>Date</code>. No fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getMinute(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.MINUTE);
	}
		
	/**
	 * Returns the hour of day of the given <code>Date</code>. No fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getHourOfDay(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.HOUR_OF_DAY);
	}
	
	/**
	 * Returns the day of month of the given <code>Date</code>. No fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getDayOfMonth(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.DATE);
	}
	
	/**
	 * Returns the week of year of the given <code>Date</code>. No fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getWeekOfYear(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.WEEK_OF_YEAR);
	}
	
	/**
	 * Returns the month of the given <code>Date</code>. No fields are changed.<br>
	 * <b>Note</b>: The first month <code>JANUARY</code> has the value 0.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getMonth(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.MONTH);
	}
	
	/**
	 * Returns the year of the given <code>Date</code>. No fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getYear(Date date) {
		calendar.setTime(date);
		return calendar.get(Calendar.YEAR);
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the minute set to the specified value.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setMinute(Date date, int value) {
		if ( value < 0 || value > 59 ) {
			return null;
		}
		calendar.setTime(date);
		calendar.set(Calendar.MINUTE, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the hour of day set to the specified value.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setHourOfDay(Date date, int value) {
		if ( value < 0 || value > 23 ) {
			return null;
		}
		calendar.setTime(date);
		calendar.set(Calendar.HOUR_OF_DAY, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the day of week set to the specified value.
	 * Monday has the value 1, Tuesday value 2, and so on. Sunday has value 7.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setDayOfWeek(Date date, int value) {
		calendar.setTime(date);
		if ( value ==  1 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
		} else if ( value == 2 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
		} else if ( value == 3 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY);
		} else if ( value == 4 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.THURSDAY);
		} else if ( value == 5 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
		} else if ( value == 6 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
		} else if ( value == 7 ) {
			calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
		} else {
			return null;
		}
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the day of month set to the specified value.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setDayOfMonth(Date date, int value) {
		if ( value < 1 || value > getNumberOfDaysInMonth(date) ) {
			return null;
		}
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_MONTH, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the week of year set to the specified value.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setWeekOfYear(Date date, int value) {
		if ( value < 1 || value > 52 ) {
			return null;
		}
		calendar.setTime(date);
		calendar.set(Calendar.WEEK_OF_YEAR, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the month set to the specified value.
	 * No other fields are changed.<br>
	 * <b>Note</b>: The first month <code>JANUARY</code> has the value 0.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setMonth(Date date, int value) {
		if ( value < 0 || value > 11 ) {
			return null;
		}
		calendar.setTime(date);
		calendar.set(Calendar.MONTH, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a new <code>Date</code> from the given <code>Date</code> with the year set to the specified value.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @param value - <code>int</code>
	 * @return <code>Date</code>
	 */
	public Date setYear(Date date, int value) {
		calendar.setTime(date);
		calendar.set(Calendar.YEAR, value);
		return calendar.getTime();
	}
	
	/**
	 * Returns a <code>String</code> with the name of the day of week of the given <code>Date</code>.
	 * @param date - <code>Date</code>
	 * @return <code>String</code>
	 */
	public String getDayName(Date date) {
		calendar.setTime(date);
		String dayNames[] = new DateFormatSymbols().getWeekdays();
		return dayNames[calendar.get(Calendar.DAY_OF_WEEK)];
	}
	
	/**
	 * Returns a <code>String</code> with the name of the month of the given <code>Date</code>.
	 * @param date - <code>Date</code>
	 * @return <code>String</code>
	 */
	public String getMonthName(Date date) {
		calendar.setTime(date);
		String monthNames[] = new DateFormatSymbols().getMonths();
		return monthNames[calendar.get(Calendar.MONTH)];
	}
	
	/**
	 * Sets the day of week of the given <code>Date</code> to 1. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code> representing the time of the first day of month
	 */
	public Date getFirstDayOfWeek(Date date) {
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
		return calendar.getTime();
	}
	
	/**
	 * Sets the day of month of the given <code>Date</code> to 1. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code> representing the time of the first day of month
	 */
	public Date getFirstDayOfMonth(Date date) {
		calendar.setTime(date);
		calendar.set(Calendar.DAY_OF_MONTH, 1);
		return calendar.getTime();
	}

	/**
	 * Sets the day of month of the given <code>Date</code> to the last day of that month.
	 * No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getLastDayOfMonth(Date date) {
		return getDayOfMonth(getPreviousDay(getFirstDayOfMonth(getNextMonth(date))));
	}
	
	/**
	 * Returns the number of days in the month given represented by the given <code>Date</code>
	 * @param date - <code>Date</code>
	 * @return <code>int</code>
	 */
	public int getNumberOfDaysInMonth(Date date) {
		return getLastDayOfMonth(date);
	}
	
	/**
	 * Adds 1 minute to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextMinute(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.MINUTE, 1);
		return calendar.getTime();
	}

	/**
	 * Adds 1 hour to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextHour(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.HOUR_OF_DAY, 1);
		return calendar.getTime();
	}
	
	/**
	 * Adds 1 hour to the given <code>Date</code>, and sets the minute, second and millisecond to 0. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextWholeHour(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.HOUR_OF_DAY, 1);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}
	
	/**
	 * Adds 1 day to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextDay(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.DAY_OF_MONTH, 1);
		return calendar.getTime();
	}

	/**
	 * Adds 1 week to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextWeek(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.WEEK_OF_YEAR, 1);
		return calendar.getTime();
	}
	
	/**
	 * Adds 1 month to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextMonth(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.MONTH, 1);
		return calendar.getTime();
	}
	
	/**
	 * Adds 1 year to the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getNextYear(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.YEAR, 1);
		return calendar.getTime();
	}
	
	/**
	 * Subtracts 1 minute from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousMinute(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.MINUTE, -1);
		return calendar.getTime();
	}
	
	/**
	 * Subtracts 1 hour from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousHour(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.HOUR_OF_DAY, -1);
		return calendar.getTime();
	}
	
	/**
	 * Subtracts 1 day from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousDay(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.DAY_OF_MONTH, -1);
		return calendar.getTime();
	}

	/**
	 * Subtracts 1 week from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousWeek(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.WEEK_OF_YEAR, -1);
		return calendar.getTime();
	}
	
	/**
	 * Subtracts 1 month from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousMonth(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.MONTH, -1);
		return calendar.getTime();
	}
	
	/**
	 * Subtracts 1 year from the given <code>Date</code>. No other fields are changed.
	 * @param date - <code>Date</code>
	 * @return <code>Date</code>
	 */
	public Date getPreviousYear(Date date) {
		calendar.setTime(date);
		calendar.add(Calendar.YEAR, -1);
		return calendar.getTime();
	}

	/**
	 * Returns a <code>String</code> array holding the names of the weekdays.
	 * @return <code>String[]</code>
	 */
	public String[] getDayNames() {
		return getDayNames(false);
	}
	
	/**
	 * Returns a <code>String</code> array holding the names of the weekdays.
	 * Day names are only represented by their first three letters.
	 * @return <code>String[]</code>
	 */
	public String[] getShortDayNames() {
		return getDayNames(true);
	}
	
	/**
	 * Returns a <code>String</code> array holding the names of the months.
	 * @return <code>String[]</code>
	 */
	public String[] getMonthNames() {
		DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(Locale.getDefault());
		return dateFormatSymbols.getMonths();
	}
	
	/**
	 * Returns a <code>String</code> array holding the names of the weekdays. The given boolean
	 * indicates whether to return short day names or not.
	 * @param shortName - <code>boolean</code>
	 * @return <code>String[]</code>
	 */
	private String[] getDayNames(boolean shortName) {
		String[] dayNames = new String[7];
		String[] weekDays = new String[7];
		DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(Locale.getDefault());
		if ( shortName ) {
			weekDays = dateFormatSymbols.getShortWeekdays();
		} else {
			weekDays = dateFormatSymbols.getWeekdays();
		}
		int day = calendar.getFirstDayOfWeek();
		for (int i = 0 ; i < 7 ; i++) {
			dayNames[i] = weekDays[day];
			if ( day < 7 ) {
				day++;
		    } else {
		    	day -= 6;
		    }
		}
		return dayNames;
	}
	
	/**
	 * Takes in to <code>Date</code> objects to be compared whether they represent the same time.
	 * The given boolean indicates whether the comparison should be done only on the day, month and year.
	 * If the boolean is set to false, the <code>Date</code>s will be compared down to the milliseconds.
	 * @param firstDate
	 * @param secondDate
	 * @param compareOnlyDayMonthYear
	 * @return <code>boolean</code>
	 */
	public boolean isSameDate(Date firstDate, Date secondDate, boolean compareOnlyDayMonthYear) {
		if ( compareOnlyDayMonthYear ) {
			if ( getDayOfMonth(firstDate) == getDayOfMonth(secondDate) && getMonth(firstDate) == getMonth(secondDate) 
				&& getYear(firstDate) == getYear(secondDate) )
				return true;
			return false;
		}
		return firstDate.equals(secondDate);
	}

	/**
	 * Checks whether the first given <code>Date</code>is before the second given <code>Date</code> in time.
	 * The given boolean indicates whether the comparison should be done only on the day, month and year.
	 * If the boolean is set to false, the <code>Date</code>s will be compared down to the milliseconds.
	 * @param firstDate
	 * @param secondDate
	 * @param compareOnlyDayMonthYear
	 * @return <code>boolean</code>
	 */
	public boolean isFirstDateBeforeSecondDate(Date firstDate, Date secondDate, boolean compareOnlyDayMonthYear) {
		if ( compareOnlyDayMonthYear ) {
			if ( getYear(firstDate) < getYear(secondDate) ) {
				return true;
			} else if ( getYear(firstDate) == getYear(secondDate) ) {
				if ( getMonth(firstDate) < getMonth(secondDate) ) {
					return true;
				} else if ( getMonth(firstDate) == getMonth(secondDate) ) {
					if ( getDayOfMonth(firstDate) < getDayOfMonth(secondDate) ) {
						return true;
					}
				}
			}
			return false;
		}
		return firstDate.before(secondDate);
	}
	
	public boolean isSameDate(Date firstDate, Date secondDate, boolean compareYear, boolean compareMonth,
			boolean compareDayOfMonth, boolean compareHourOfDay, boolean compareMinute) {
		if ( compareYear )
			if ( !(getYear(firstDate) == getYear(secondDate)) )
				return false;
		if ( compareMonth )
			if ( !(getMonth(firstDate) == getMonth(secondDate)) )
				return false;
		if ( compareDayOfMonth )
			if ( !(getDayOfMonth(firstDate) == getDayOfMonth(secondDate)) )
				return false;
		if ( compareHourOfDay )
			if ( !(getHourOfDay(firstDate) == getHourOfDay(secondDate)) )
				return false;
		if ( compareMinute )
			if ( !(getMinute(firstDate) == getMinute(secondDate)) )
				return false;
		return true;
	}
	
	public String printDate(Date date, boolean year, boolean month, boolean dayOfMonth, boolean hourOfDay, boolean minute) {
		String ret = "";
		if ( month )
			ret += getMonthName(date) + " ";
		if ( dayOfMonth )
			ret += getDayOfMonth(date) + " ";
		if ( hourOfDay ) {
			int hour = getHourOfDay(date);
			String sHour = Integer.toString(hour);
			if ( hour < 10 ) {
				sHour = "0" + sHour;
			}
			ret += sHour + ":";
		}
		if ( minute ) {
			int minute1 = getMinute(date);
			String sMinute = Integer.toString(minute1);
			if ( minute1 < 10 ) {
				sMinute = "0" + sMinute;
			}
			ret += sMinute + " ";
		}
		if ( year )
			ret += (getYear(date));
		return ret;
	}

}
